<?php
/**
 *  DB - A simple database class 
 *
 * @author		Author: Vivek Wicky Aswal. (https://twitter.com/#!/VivekWickyAswal)
 * @git 		https://github.com/indieteq/PHP-MySQL-PDO-Database-Class
 * @version      0.2ab
 *
 */
require("Log.class.php");
class DB
{
	# @object, The PDO object
	private $pdo;

	# @object, PDO statement object
	private $sQuery;
	private $sQueries;

	# @array,  The database settings
	private $settings;

	# @bool ,  Connected to the database
	private $bConnected = false;

	# @object, Object for logging exceptions	
	private $log;

	# @array, The parameters of the SQL query
	private $parameters;
		
       /**
	*   Default Constructor 
	*
	*	1. Instantiate Log class.
	*	2. Connect to database.
	*	3. Creates the parameter array.
	*/
		public function __construct($config)
		{ 			
			$this->log = new Log();	
			$this->Connect($config);
			$this->parameters = array();
		}
	
       /**
	*	This method makes connection to the database.
	*	
	*	1. Reads the database settings from a ini file. 
	*	2. Puts  the ini content into the settings array.
	*	3. Tries to connect to the database.
	*	4. If connection failed, exception is displayed and a log file gets created.
	*/
	  private function GetConfig($configFile){
	    return parse_ini_file($configFile);
	  }
	  private function SetSettings($settings){
	    $this->settings = $settings;
	  }
		private function Connect($configFile=false){
		  if( is_string($configFile) ) $this->settings = $this->GetConfig($configFile);
		  elseif( is_array($configFile) ) $this->settings = $configFile;
		  
			$dsn = 'mysql:dbname='.$this->settings["dbname"].';host='.$this->settings["host"].'';
			try {
				# Read settings from INI file, set UTF8
				$snm=(!empty($this->settings['set_names']))?$this->settings['set_names']:'utf8';
				$this->pdo = new PDO($dsn, $this->settings["user"], $this->settings["password"], array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES $snm"));

				# We can now log any exceptions on Fatal error.
				$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

				# Disable emulation of prepared statements, use REAL prepared statements instead.
				//$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
				
				# Connection succeeded, set the boolean to true.
				$this->bConnected = true;
			}	catch (PDOException $e)	{
				# Write into log
				echo $this->ExceptionLog($e->getMessage()); die();
			}
		}
	/*
	 *   You can use this little method if you want to close the PDO connection
	 *
	 */
	 	public function CloseConnection()
	 	{
	 		# Set the PDO object to null to close the connection
	 		# http://www.php.net/manual/en/pdo.connections.php
	 		$this->pdo = null;
	 	}
		
       /**
	*	Every method which needs to execute a SQL query uses this method.
	*	
	*	1. If not connected, connect to the database.
	*	2. Prepare Query.
	*	3. Parameterize Query.
	*	4. Execute Query.	
	*	5. On exception : Write Exception into the log + SQL query.
	*	6. Reset the Parameters.
	*/	
	  protected function genQueryHash($query){
	    return md5( $query );
		}
		
		//http://paul-m-jones.com/archives/243
		protected function getPlaceholders($query){
		  if(preg_match_all(
			    "/\W:([a-zA-Z_][a-zA-Z0-9_]+?)\W/m",
			    $query . "\n",
			    $matches
			)){
			  return $matches[1];
			}else
			  return array();
		}
		
		public function getPDO(){
		  return $this->pdo;
		}
	
	  public function Prepare($query,$deliverOptions=array()){
	    if(!$this->bConnected) { $this->Connect(); }
	    $qhash=$this->genQueryHash( $query );
	    if( empty($this->sQueries[$qhash]) ){
				# Prepare query
				$sq = $this->pdo->prepare($query,$deliverOptions);
				$ph = $this->getPlaceholders($query);
				$this->sQueries[$qhash]=array('statement'=>$sq,'placeholders'=>$ph);
			}
			return  $this->sQueries[$qhash];
		}
	
		private function Init($query,$parameters = ""){
		# Connect to database
		if(!$this->bConnected) { $this->Connect(); }
		try {
		    $q=$this->Prepare($query);
		    $this->sQuery=$q['statement'];
				# Add parameters to the parameter array
				$this->bindMore($parameters,$q['placeholders']);

				# Bind parameters
				
				if(!empty($this->parameters)) {

					foreach($this->parameters as $param=>$paramVal){
					  if( is_array($paramVal) ) {
							$val=$paramVal[0]; $typ=$paramVal[1];
						}else{
						  $val=$paramVal; $typ=PDO::PARAM_STR;
						}

						try{
						  if(!$this->sQuery->bindValue($param,$val,$typ)){
							  echo '<br />error '.$param.' '.$val;
							}
						}catch(Exception $e){
						  echo $e->getMessage();
						}
						
//echo '<hr>';$this->sQuery->debugDumpParams();
					}
				}
				

				# Execute SQL
				$this->succes 	= $this->sQuery->execute();
			} catch(PDOException $e) {
					# Write into log and display Exception
					echo $this->ExceptionLog($e->getMessage(), $query );
					die();
			}

			# Reset the parameters
			$this->parameters = array();
		}
		
       /**
	*	@void 
	*
	*	Add the parameter to the parameter array
	*	@param string $para  
	*	@param string $value 
	*/	
		public function bind($para, $value){

   		//$this->parameters[sizeof($this->parameters)] = ":" . $para . "\x7F" . $value;
   		if( substr($para, 0,1)!=':' ) $para=':'.$para;
   		$this->parameters[$para]=$value;
		}
       /**
	*	@void
	*	
	*	Add more parameters to the parameter array
	*	@param array $parray
	*/	
		public function bindMore($parray,$placeholders=array()){
			if(!is_array($parray)) return false;
			/*
			$repeat=array();
			for($i=0,$cnt=count($placeholders); $i<$cnt; $i++){
			  if(!isset($parray[ $placeholders[$i] ])) continue;
			  if(empty($repeat[ $placeholders[$i] ])) $repeat[ $placeholders[$i] ]=1;
				else{
				  $parray[ $placeholders[$i].$repeat[ $placeholders[$i] ] ]=$parray[ $placeholders[$i] ];
				  $repeat[ $placeholders[$i] ]++;
				}
			}
			*/
			
			$columns = array_keys($parray);
			
			//foreach($columns as $i => &$column)	{
			for($i=0,$cnt=count($columns); $i<$cnt; $i++){
				$this->bind($columns[$i], $parray[$columns[$i]]);
			}
		}
       /**
	*   	If the SQL query  contains a SELECT or SHOW statement it returns an array containing all of the result set row
	*	If the SQL statement is a DELETE, INSERT, or UPDATE statement it returns the number of affected rows
	*
	*   	@param  string $query
	*	@param  array  $params
	*	@param  int    $fetchmode
	*	@return mixed
	*/			
		public function query($query,$params = null, $fetchmode = PDO::FETCH_ASSOC)
		{
			$query = trim($query);

			$this->Init($query,$params);

			$rawStatement = explode(" ", $query);
			
			# Which SQL statement is used 
			$statement = strtolower($rawStatement[0]);
			
			if ($statement === 'select' || $statement === 'show') {
				return $this->sQuery->fetchAll($fetchmode);
			}
			elseif ( $statement === 'insert' ||  $statement === 'update' || $statement === 'delete' ) {
				return $this->sQuery->rowCount();	
			}	
			else {
				return NULL;
			}
		}
		
      /**
       *  Returns the last inserted id.
       *  @return string
       */	
		public function lastInsertId() {
			return $this->pdo->lastInsertId();
		}	
		
       /**
	*	Returns an array which represents a column from the result set 
	*
	*	@param  string $query
	*	@param  array  $params
	*	@return array
	*/	
		public function column($query,$params = null)
		{
			$this->Init($query,$params);
			$Columns = $this->sQuery->fetchAll(PDO::FETCH_NUM);		
			
			$column = null;

			foreach($Columns as $cells) {
				$column[] = $cells[0];
			}

			return $column;
			
		}	
       /**
	*	Returns an array which represents a row from the result set 
	*
	*	@param  string $query
	*	@param  array  $params
	*   	@param  int    $fetchmode
	*	@return array
	*/	
		public function row($query,$params = null,$fetchmode = PDO::FETCH_ASSOC)
		{
			$this->Init($query,$params);
			return $this->sQuery->fetch($fetchmode);			
		}
       /**
	*	Returns the value of one single field/column
	*
	*	@param  string $query
	*	@param  array  $params
	*	@return string
	*/	
		public function single($query,$params = null)
		{
			$this->Init($query,$params);
			return $this->sQuery->fetchColumn();
		}
       /**	
	* Writes the log and returns the exception
	*
	* @param  string $message
	* @param  string $sql
	* @return string
	*/
	private function ExceptionLog($message , $sql = "")
	{
		$exception  = 'Unhandled Exception. <br />';
		$exception .= $message;
		$exception .= "<br /> You can find the error back in the log.";

		if(!empty($sql)) {
			# Add the Raw SQL to the Log
			$message .= "\r\nRaw SQL : "  . $sql;
		}
			# Write into log
			$this->log->write($message);

		return $exception;
	}			
}
?>
